﻿/***************************************************************
 * 开发人员：网魂小兵@http://www.mozlite.com
 * 
 * 缓存管理类。
 * 
 * 最后修改：2011-10-29
 * ************************************************************/
namespace Mozlite.Caching
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web.Caching;
    using System.Threading;
    using System.Collections;
    using System.Text.RegularExpressions;

    /// <summary>
    /// 缓存管理类。
    /// </summary>
    public class Cacher
    {
        #region helper
        private Cacher() { }

        /// <summary>
        /// 设置是否允许后台缓存回调函数。
        /// </summary>
        public static bool EnableBackgroundCacheCallbacks { private get; set; }

        internal static object InternalCallback(string key)
        {
            CacheEntry cacheEntry = CacheLockbox.GetCacheEntry(key);
            if (cacheEntry == null)
            {
                return null;
            }
            CacheLoaderDelegate cacheLoader = cacheEntry.CacheLoader;
            if (cacheLoader == null)
            {
                return null;
            }
            object loadedObject = null;
            try
            {
                loadedObject = cacheLoader();
            }
            catch (Exception exception)
            {
                HandleCacheLoaderError(key, exception);
            }
            if (loadedObject != null)
            {
                DateTime now = DateTime.UtcNow;
                TimeSpan span = (TimeSpan)(DateTime.MaxValue - now);
                DateTime absoluteExpiration = (TimeSpan.Compare(span, cacheEntry.RefreshInterval) == 1) ? now.Add(cacheEntry.RefreshInterval) : DateTime.MaxValue;
                CacheStore.Insert(key, loadedObject, null, absoluteExpiration, Cache.NoSlidingExpiration, cacheEntry.CacheItemPriority, EnableBackgroundCacheCallbacks ? new CacheItemRemovedCallback(Cacher.ItemRemovedCallback) : null);
                CacheLockbox.UpdateCacheEntryLastUpdate(key, now);
            }
            return loadedObject;
        }

        internal static void ItemRemovedCallback(string key, object value, CacheItemRemovedReason reason)
        {
            if (reason == CacheItemRemovedReason.Expired)
            {
                CacheEntry cacheEntry = CacheLockbox.GetCacheEntry(key);
                DateTime utcNow = DateTime.UtcNow;
                if (cacheEntry != null && cacheEntry.LastUse.Add(cacheEntry.SlidingExpiration) > utcNow)
                {
                    CacheStore.Insert(key, value, null, utcNow.AddSeconds(Math.Min(cacheEntry.RefreshInterval.TotalSeconds, 30.0)), Cache.NoSlidingExpiration, CacheItemPriority.Low, null);
                    ThreadPool.QueueUserWorkItem(p =>
                    {
                        string pkey = p.ToString();
                        lock (CacheLockbox.GetInternalLock(pkey))
                        {
                            InternalCallback(pkey);
                        }
                    }, key);
                }
            }
        }
        /// <summary>
        /// 更新缓存项。
        /// </summary>
        /// <param name="key">缓存键。</param>
        /// <returns>返回是否更新了缓存项。</returns>
        public static bool Update(string key)
        {
            if (!CacheLockbox.ContainsCacheEntry(key))
            {
                return false;
            }
            lock (CacheLockbox.GetInternalLock(key))
            {
                InternalCallback(key);
            }
            return true;
        }
        /// <summary>
        /// 是否包含缓存项。
        /// </summary>
        /// <param name="key">缓存键。</param>
        /// <returns>返回是否包含缓存项。</returns>
        public static bool Contains(string key)
        {
            return CacheLockbox.ContainsCacheEntry(key);
        }
        /// <summary>
        /// 获取缓存实例锁对象。
        /// </summary>
        /// <param name="key">缓存键。</param>
        /// <returns>返回缓存项锁对象。</returns>
        public static object GetLock(string key)
        {
            return CacheLockbox.GetLock(key);
        }
        /// <summary>
        /// 获取缓存实例锁对象。
        /// </summary>
        /// <param name="key">缓存键。</param>
        /// <param name="refreshIntervalSeconds">检测时间间隔秒钟。</param>
        /// <param name="slidingExpirationSeconds">相对过期时间秒钟。</param>
        /// <param name="loaderDelegate">缓存加载器。</param>
        /// <param name="priority">缓存优先级。</param>
        /// <returns>返回缓存项锁对象。</returns>
        public static object GetLock(string key, int refreshIntervalSeconds, int slidingExpirationSeconds, CacheLoaderDelegate loaderDelegate, CacheItemPriority priority = CacheItemPriority.Normal)
        {
            return GetLock(key, TimeSpan.FromSeconds((double)(refreshIntervalSeconds)), TimeSpan.FromSeconds((double)(slidingExpirationSeconds)), loaderDelegate, priority);
        }
        /// <summary>
        /// 获取缓存实例锁对象。
        /// </summary>
        /// <param name="key">缓存键。</param>
        /// <param name="refreshInterval">检测时间间隔刻度。</param>
        /// <param name="slidingExpiration">相对过期时间刻度。</param>
        /// <param name="loaderDelegate">缓存加载器。</param>
        /// <param name="priority">缓存优先级。</param>
        /// <returns>返回缓存项锁对象。</returns>
        public static object GetLock(string key, TimeSpan refreshInterval, TimeSpan slidingExpiration, CacheLoaderDelegate loaderDelegate, CacheItemPriority priority = CacheItemPriority.Normal)
        {
            return CacheLockbox.GetLock(key, refreshInterval, slidingExpiration, loaderDelegate, priority);
        }
        #endregion

        #region exceptions
        /// <summary>
        /// 缓存加载器错误处理代理。
        /// </summary>
        /// <param name="cacheKey">缓存键。</param>
        /// <param name="e">错误实例对象。</param>
        public delegate void CacheLoaderErrorDelegate(string cacheKey, Exception e);

        private static CacheLoaderErrorDelegate cacheLoaderErrorDelegate;
        /// <summary>
        /// 设置缓存错误处理代理方法，只能设置一次，否则将抛出错误。
        /// </summary>
        /// <param name="handler">缓存错误处理代理方法。</param>
        /// <exception cref="T:System.Exception">The CacheLoaderDelegate should only be set once within an app.</exception>
        public static void SetCacheLoaderErrorHandler(CacheLoaderErrorDelegate handler)
        {
            if (cacheLoaderErrorDelegate != null)
            {
                throw new Exception("The CacheLoaderDelegate should only be set once within an app.");
            }
            if (handler != null)
            {
                cacheLoaderErrorDelegate = handler;
            }
        }
        /// <summary>
        /// 处理缓存错误。
        /// </summary>
        /// <param name="key">缓存键。</param>
        /// <param name="exception">错误实例对象。</param>
        public static void HandleCacheLoaderError(string key, Exception exception)
        {
            if (cacheLoaderErrorDelegate != null)
            {
                try
                {
                    cacheLoaderErrorDelegate(key, exception);
                }
                catch
                {
                }
            }
        }
        #endregion

        #region remove
        /// <summary>
        /// 清楚所有缓存。
        /// </summary>
        public static void Clear()
        {
            IDictionaryEnumerator enumerator = CacheStore.GetEnumerator();
            ArrayList list = new ArrayList();
            while (enumerator.MoveNext())
            {
                list.Add(enumerator.Key);
            }
            foreach (string str in list)
            {
                Remove(str);
            }
        }
        /// <summary>
        /// 移除缓存。
        /// </summary>
        /// <param name="key">缓存键。</param>
        public static void Remove(string key)
        {
            CacheLockbox.Remove(key);
            CacheStore.Remove(key);
        }
        /// <summary>
        /// 通过正则表达式移除缓存。
        /// </summary>
        /// <param name="pattern">缓存键正则表达式。</param>
        public static void RemoveByPattern(string pattern)
        {
            IDictionaryEnumerator enumerator = CacheStore.GetEnumerator();
            Regex regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
            while (enumerator.MoveNext())
            {
                string input = enumerator.Key.ToString();
                if (regex.IsMatch(input))
                {
                    Remove(input);
                }
            }
        }
        #endregion

        #region insert 
        /// <summary>
        /// 插入缓存对象，相对过期时间（没有使用当前对象开始计时）为3分钟。
        /// </summary>
        /// <param name="key">缓存键。</param>
        /// <param name="obj">缓存对象。</param>
        /// <param name="dep">缓存依赖项。</param>
        public static void Insert(string key, object obj, CacheDependency dep = null)
        {
            CacheStore.Insert(key, obj, dep, CacheStore.NoAbsoluteExpiration, TimeSpan.FromSeconds(Consts.MinuteSeconds * 3), CacheItemPriority.Normal, null);
        }
        /// <summary>
        /// 插入缓存对象。
        /// </summary>
        /// <param name="key">缓存键。</param>
        /// <param name="obj">缓存对象。</param>
        /// <param name="seconds">缓存时间。</param>
        /// <param name="priority">缓存优先级。</param>
        public static void Insert(string key, object obj, int seconds, CacheItemPriority priority = CacheItemPriority.Normal)
        {
            Insert(key, obj, null, seconds, priority);
        }
        /// <summary>
        /// 插入缓存对象。
        /// </summary>
        /// <param name="key">缓存键。</param>
        /// <param name="obj">缓存对象。</param>
        /// <param name="dep">缓存依赖项。</param>
        /// <param name="seconds">缓存时间。</param>
        /// <param name="priority">缓存优先级。</param>
        /// <param name="onRemoveCallback">当缓存项移除时执行的方法。</param>
        public static void Insert(string key, object obj, CacheDependency dep, int seconds, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback onRemoveCallback = null)
        {
            CacheStore.Insert(key, obj, dep, DateTime.UtcNow.AddSeconds((double)seconds), Cache.NoSlidingExpiration, priority, onRemoveCallback);
        }
        /// <summary>
        /// 永久性插入缓存。
        /// </summary>
        /// <param name="key">缓存键。</param>
        /// <param name="obj">缓存对象实例。</param>
        /// <param name="dep">缓存依赖项。</param>
        public static void Max(string key, object obj, CacheDependency dep = null)
        {
            CacheStore.Insert(key, obj, dep, DateTime.MaxValue, Cache.NoSlidingExpiration, CacheItemPriority.AboveNormal, null);
        }
        /// <summary>
        /// 永久性插入不可移除缓存。
        /// </summary>
        /// <param name="key">缓存键。</param>
        /// <param name="obj">缓存对象实例。</param>
        /// <param name="dep">缓存依赖项。</param>
        public static void Permanent(string key, object obj, CacheDependency dep = null)
        {
            CacheStore.Insert(key, obj, dep, DateTime.MaxValue, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null);
        }
        #endregion

        #region get
        /// <summary>
        /// 获取缓存对象。
        /// </summary>
        /// <returns>返回缓存对象。</returns>
        public static object Get(string key)
        {
            return CacheStore.Get(key);
        }

        /// <summary>
        /// 获取缓存对象。
        /// </summary>
        /// <typeparam name="T">缓存类。</typeparam>
        /// <param name="key">缓存键。</param>
        /// <param name="loader">从数据提供者中载入实例对象的方法。</param>
        /// <returns>返回缓存对象。</returns>
        public static T Get<T>(string key, CacheLoaderDelegate loader)
        {
            return CacheStore.Get<T>(key, loader);
        }

        /// <summary>
        /// 获取缓存对象。
        /// </summary>
        /// <typeparam name="T">缓存类。</typeparam>
        /// <param name="key">缓存键。</param>
        /// <returns>返回缓存对象。</returns>
        /// <remarks>使用此方法需要注意，必须已经确定缓存中具有该键的缓存项，否则将抛出错误！</remarks>
        public static T Get<T>(string key) where T : class
        {
            if (CacheLockbox.ContainsCacheEntry(key))
            {
                lock (CacheLockbox.GetLock(key))
                {
                    T value = Get(key) as T;
                    if (value == null)
                        value = InternalCallback(key) as T;
                    return value;
                }
            }
            throw new Exception("CacheEntry for key '" + key + "' doesn't exist.  Please call a different overload of Cacher.Get<T>() to set the CacheEntry properties.");
        }
        /// <summary>
        /// 获取缓存实例对象。
        /// </summary>
        /// <typeparam name="T">缓存类。</typeparam>
        /// <param name="key">缓存键。</param>
        /// <param name="refreshIntervalSeconds">检测时间间隔秒钟。</param>
        /// <param name="slidingExpirationSeconds">相对过期时间秒钟。</param>
        /// <param name="loaderDelegate">缓存加载器。</param>
        /// <param name="priority">缓存优先级。</param>
        /// <returns>返回缓存对象。</returns>
        public static T Get<T>(string key, int refreshIntervalSeconds, int slidingExpirationSeconds, CacheLoaderDelegate loaderDelegate, CacheItemPriority priority = CacheItemPriority.Normal) where T : class
        {
            return Get<T>(key, TimeSpan.FromSeconds((double)refreshIntervalSeconds), TimeSpan.FromSeconds((double)slidingExpirationSeconds), loaderDelegate, priority);
        }
        /// <summary>
        /// 获取缓存实例对象。
        /// </summary>
        /// <typeparam name="T">缓存类。</typeparam>
        /// <param name="key">缓存键。</param>
        /// <param name="refreshInterval">检测时间间隔刻度。</param>
        /// <param name="slidingExpiration">相对过期时间刻度。</param>
        /// <param name="loaderDelegate">缓存加载器。</param>
        /// <param name="priority">缓存优先级。</param>
        /// <returns>返回缓存对象。</returns>
        public static T Get<T>(string key, TimeSpan refreshInterval, TimeSpan slidingExpiration, CacheLoaderDelegate loaderDelegate, CacheItemPriority priority = CacheItemPriority.Normal) where T : class
        {
            lock (CacheLockbox.GetLock(key, refreshInterval, slidingExpiration, loaderDelegate, priority))
            {
                T value = Get(key) as T;
                if (value == null)
                    value = InternalCallback(key) as T;
                return value;
            }
        }
        #endregion
    }
}
